/********************************************************************+
# Copyright 2018-2019 Daniel 'grindhold' Brendle
#
# This file is part of phex.
#
# phex is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# phex is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with phex.
# If not, see http://www.gnu.org/licenses/.
*********************************************************************/

#include <Elementary.h>
#include <Evas_GL.h>
#include <phexfile.h>

#include "common.h"
#include "context.h"
#include "mol_view.h"
#include "graph_view.h"

// resize callback gets called every time object is resized
static void
_resize_gl(Evas_Object *obj)
{
   int w, h;
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   //Evas_GL_API *gl = gld->glapi;
   float aspect;

   elm_glview_size_get(obj, &w, &h);
   customLoadIdentity(gld->view);

   if (w > h)
     {
        aspect = (float) w / h;
        customFrustum(gld->view, -1.0 * aspect, 1.0 * aspect, -1.0, 1.0, -5.0, 5.0);
     }
   else
     {
        aspect = (float) h / w;
        customFrustum(gld->view, -1.0, 1.0, -1.0 * aspect, 1.0 * aspect, -5.0, 5.0);
     }
}
// delete callback gets called when glview is deleted
static void
_del_gl(Evas_Object *obj)
{
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   if (!gld)
     {
        printf("Unable to get GLData. \n");
        return;
     }

   Evas_GL_API *gl = gld->glapi;
   gl->glDeleteShader(gld->vtx_shader);
   gl->glDeleteShader(gld->fgmt_shader);
   gl->glDeleteProgram(gld->program);
   evas_object_data_del((Evas_Object*) obj, "..gld");
}

static void _coord_changed_cb(void *data, Evas_Object* obj, void* event_info) {
    update_table(data);
}

static void _zoom_cb(void *data, Evas* e,Evas_Object* obj, void* event_info) {
    Phexdata* ctx = (Phexdata*)data;
    ctx->graph_zoom += 0.1 * ((Evas_Event_Mouse_Wheel*)event_info)->z;
}

// just need to notify that glview has changed so it can render
static Eina_Bool
_anim(void *data)
{
   elm_glview_changed_set(data);
   return EINA_TRUE;
}
static void
_mouse_down_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
{
   GLData *gld = data;
   gld->mouse_down = EINA_TRUE;
}

static void
_mouse_up_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
{
   GLData *gld = data;
   gld->mouse_down = EINA_FALSE;
}

static void
_mouse_move_cb(void *data, Evas *e , Evas_Object *obj , void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   ev = (Evas_Event_Mouse_Move *)event_info;
      GLData *gld = data;
   float dx = 0, dy = 0;

   if(gld->mouse_down)
     {
        dx = ev->cur.canvas.x - ev->prev.canvas.x;
        dy = ev->cur.canvas.y - ev->prev.canvas.y;
        gld->xangle += dy;
        gld->yangle += dx;
     }
}

static void
_on_done(void *data, Evas_Object *obj, void *event_info)
{
   evas_object_del((Evas_Object*)data);
   elm_exit();
}
static void
_del(void *data, Evas *evas, Evas_Object *obj, void *event_info)
{
   Ecore_Animator *ani = evas_object_data_get(obj, "ani");
   ecore_animator_del(ani);
}

static Eina_Bool
animate_reset_cb(void *data, double pos)
{
   GLData *gld = data;
   double frame = pos;
   float x, y, z;

   frame = ecore_animator_pos_map(pos, ECORE_POS_MAP_BOUNCE, 1.8, 7);
   x = gld->slx_value * (1 - frame) + 0.75 * frame;
   y = gld->sly_value * (1 - frame) + 0.75 * frame;
   z = gld->slz_value * (1 - frame) + 0.75 * frame;

   elm_slider_value_set(gld->slx, x);
   elm_slider_value_set(gld->sly, y);
   elm_slider_value_set(gld->slz, z);

   return EINA_TRUE;
}

static void btn_reset_cb(void *data, Evas_Object *obj,  void *event_info)
{
   GLData *gld = data;
   gld->slx_value = elm_slider_value_get(gld->slx);
   gld->sly_value = elm_slider_value_get(gld->sly);
   gld->slz_value = elm_slider_value_get(gld->slz);
   ecore_animator_timeline_add(1, animate_reset_cb, gld);
}

EAPI_MAIN int
elm_main(int argc, char **argv)
{
   Evas_Object *win, *inner_box, *graph_gl, *mol_gl, *btn_reset;
   Evas_Object *glview_pane, *ctrl_pane, *main_pane;
   Ecore_Animator *ani;
   GLData *graph_gld, *mol_gld = NULL;
   if (!(graph_gld = calloc(1, sizeof(GLData)))) return 1;
   if (!(mol_gld = calloc(1, sizeof(GLData)))) return 1;
   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
   win = elm_win_util_standard_add("glview simple", "phex");
   elm_win_autodel_set(win, EINA_TRUE);
   main_pane = elm_panes_add(win);
   glview_pane = elm_panes_add(main_pane);
   ctrl_pane = elm_panes_add(main_pane);
   evas_object_show(glview_pane);
   evas_object_show(main_pane);
   evas_object_show(ctrl_pane);
   inner_box = elm_box_add(ctrl_pane);
   evas_object_size_hint_weight_set(inner_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_show(inner_box);

   //-//-//-// THIS IS WHERE GL INIT STUFF HAPPENS (ALA EGL)
   //-//
   // create a new glview object
   graph_gl = elm_glview_add(win);
   mol_gl = elm_glview_add(win);
   graph_gld->glapi = elm_glview_gl_api_get(graph_gl);
   mol_gld->glapi = elm_glview_gl_api_get(mol_gl);
   evas_object_size_hint_align_set(graph_gl, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_size_hint_weight_set(graph_gl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(mol_gl, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_size_hint_weight_set(mol_gl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   // mode is simply for supporting alpha, depth buffering, and stencil
   // buffering.
   elm_glview_mode_set(graph_gl, ELM_GLVIEW_ALPHA | ELM_GLVIEW_DEPTH);
   elm_glview_mode_set(mol_gl, ELM_GLVIEW_ALPHA | ELM_GLVIEW_DEPTH);
   // resize policy tells glview what to do with the surface when it
   // resizes.  ELM_GLVIEW_RESIZE_POLICY_RECREATE will tell it to
   // destroy the current surface and recreate it to the new size
   elm_glview_resize_policy_set(graph_gl, ELM_GLVIEW_RESIZE_POLICY_RECREATE);
   elm_glview_resize_policy_set(mol_gl, ELM_GLVIEW_RESIZE_POLICY_RECREATE);
   // render policy tells glview how it would like glview to render
   // graph_gl code. ELM_GLVIEW_RENDER_POLICY_ON_DEMAND will have the graph_gl
   // calls called in the pixel_get callback, which only gets called
   // if the object is visible, hence ON_DEMAND.  ALWAYS mode renders
   // it despite the visibility of the object.
   elm_glview_render_policy_set(graph_gl, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND);
   elm_glview_render_policy_set(mol_gl, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND);
   // initialize callback function gets registered here
   elm_glview_init_func_set(graph_gl, _init_gl);
   elm_glview_init_func_set(mol_gl, _init_mol_gl);
   // delete callback function gets registered here
   elm_glview_del_func_set(graph_gl, _del_gl);
   elm_glview_resize_func_set(graph_gl, _resize_gl);
   elm_glview_render_func_set(graph_gl, _draw_gl);

   elm_glview_del_func_set(mol_gl, _del_gl);
   elm_glview_resize_func_set(mol_gl, _resize_gl);
   elm_glview_render_func_set(mol_gl, _draw_mol_gl);


   Evas_Object* energy_table = elm_table_add(ctrl_pane);
   evas_object_size_hint_weight_set(energy_table, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   //-//
   //-//-//-// END GL INIT BLOB
   elm_panes_horizontal_set(main_pane, EINA_TRUE);
   elm_object_part_content_set(main_pane, "left", glview_pane);
   elm_object_part_content_set(main_pane, "right", ctrl_pane);
   elm_object_part_content_set(ctrl_pane, "left", inner_box);
   elm_object_part_content_set(ctrl_pane, "right", energy_table);
   elm_object_part_content_set(glview_pane, "left",graph_gl);
   elm_object_part_content_set(glview_pane, "right", mol_gl);
   evas_object_show(graph_gl);
   elm_object_focus_set(graph_gl, EINA_TRUE);
   evas_object_show(mol_gl);
   elm_object_focus_set(mol_gl, EINA_TRUE);
   // animating - just a demo. as long as you trigger an update on the image
   // object via elm_glview_changed_set() it will be updated.
   //
   // NOTE: if you delete graph_gl, this animator will keep running trying to access
   // graph_gl so you'd better delete this animator with ecore_animator_del().
   ani = ecore_animator_add(_anim, graph_gl);
   ani = ecore_animator_add(_anim, mol_gl);
   evas_object_data_set(graph_gl, "ani", ani);
   evas_object_data_set(mol_gl, "ani", ani);
   evas_object_data_set(graph_gl, "graph_gld", graph_gld);
   evas_object_event_callback_add(graph_gl, EVAS_CALLBACK_DEL, _del, graph_gl);
   evas_object_event_callback_add(graph_gl, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, graph_gld);
   evas_object_event_callback_add(graph_gl, EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, graph_gld);
   evas_object_event_callback_add(graph_gl, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move_cb, graph_gld);

   evas_object_data_set(mol_gl, "graph_gld", mol_gld);
   evas_object_event_callback_add(mol_gl, EVAS_CALLBACK_DEL, _del, mol_gl);
   evas_object_event_callback_add(mol_gl, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, mol_gld);
   evas_object_event_callback_add(mol_gl, EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, mol_gld);
   evas_object_event_callback_add(mol_gl, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move_cb, mol_gld);
   /* Set rotation variables */
   graph_gld->xangle = 45.0f;
   graph_gld->yangle = 45.0f;
   graph_gld->zangle = 0.0f;
   graph_gld->mouse_down = EINA_FALSE;
   graph_gld->initialized = EINA_FALSE;

   // Reset button
   btn_reset = elm_button_add(inner_box);
   elm_object_text_set(btn_reset, "Reset");
   evas_object_smart_callback_add(btn_reset, "clicked", btn_reset_cb, graph_gld);
   evas_object_size_hint_align_set(btn_reset, EVAS_HINT_FILL, 0);
   elm_box_pack_end(inner_box, btn_reset);
   evas_object_show(btn_reset);

   evas_object_resize(glview_pane, 800, 800);
   evas_object_resize(main_pane, 800, 800);
   evas_object_resize(ctrl_pane, 800, 800);
   evas_object_resize(energy_table, 800, 800);
   evas_object_resize(win, 800, 800);
   evas_object_show(win);

   GError* err = NULL;
   char* simpath = "test.json";
   if (argc == 2) {
        simpath = *(argv+1);
   }
   Phexdata* ctx;
   ctx = malloc(sizeof(Phexdata));
   ctx->sim =phexfile_simulation_load_from_json_file(simpath, &err);
   if (err != NULL) {
     g_error(err->message);
   }
   elm_table_padding_set(energy_table, 40, 40);

   GList* excitations = phexfile_simulation_get_excitations(ctx->sim);
   int n_exc = (int)g_list_length(excitations);

   ctx->energy_labels = malloc((n_exc+1)*(n_exc+1)*sizeof(Evas_Object*));

   for (int i = -1; i < n_exc; i++) {
        for (int j = -1; j < n_exc; j++) {
            int tbl_row = i + 1;
            int tbl_col = j + 1;
            if (i == j) continue;
            Evas_Object* label = elm_label_add(energy_table);
            if (i == -1) {
                char* color = color_f2h(palette[j*4], palette[j*4+1], palette[j*4+2]);
                char* labeltext = colored_text(g_list_nth_data(excitations, j), color);
                elm_object_text_set(label, labeltext);
                free(color);
                free(labeltext);
                ctx->energy_labels[i*(n_exc+1) + j] = label;
            } else if (j == -1) {
                char* color = color_f2h(palette[i*4], palette[i*4+1], palette[i*4+2]);
                char* labeltext = colored_text(g_list_nth_data(excitations, i), color);
                elm_object_text_set(label, labeltext);
                free(color);
                free(labeltext);
                ctx->energy_labels[i*(n_exc+1) + j] = label;
            } else {
                ctx->energy_labels[i*(n_exc+1) + j] = label;
            }
            evas_object_show(label);
            elm_table_pack(energy_table, label, tbl_col, tbl_row, 2, 2);
        }
   }
   evas_object_show(energy_table);

   ctx->coord_len = g_list_length(
        phexfile_simulation_get_coordinates(ctx->sim)
    );
   mol_gld->ctx = graph_gld->ctx = ctx;

   GList* methods = phexfile_simulation_get_methods(ctx->sim);
   guint methods_length = g_list_length(methods);

   Evas_Object* methodbox = elm_box_add(inner_box);
   elm_box_horizontal_set(methodbox, EINA_TRUE);
   for (unsigned int i = 0; i < methods_length; i++) {
        Evas_Object* radio;
        radio = elm_radio_add(methodbox);
        gchar* radiotext = g_list_nth_data(methods, i);
        elm_object_text_set(radio, radiotext);
        elm_radio_state_value_set(radio, i);
        if (i == 0) {
            ctx->radio_method = radio;
        } else {
            elm_radio_group_add(radio, ctx->radio_method);
        }
        elm_box_pack_end(methodbox, radio);
        evas_object_show(radio);
    }
    evas_object_show(methodbox);
   elm_box_pack_end(inner_box, methodbox);

   ctx->coord_sliders = malloc(ctx->coord_len*sizeof(Evas_Object*));

    if (ctx->coord_len < 2) {
        g_error("Coord length must be equal or greater 2. Cannot handle 1-dimensional results at the moment…");
    }

   for (int i = 0; i < ctx->coord_len; i++) {
       PhexfileCoordinate* coord = g_list_nth_data(phexfile_simulation_get_coordinates(ctx->sim), i);
       Evas_Object* cosl;
       Evas_Object* coordbox;
       Evas_Object* coord_radio_x;
       Evas_Object* coord_radio_y;
       Evas_Object* coord_label;
       coordbox = elm_box_add(inner_box);
       elm_box_horizontal_set(coordbox, EINA_TRUE);
       cosl = *((ctx->coord_sliders)+i) = elm_slider_add(coordbox);
       coord_label = elm_label_add(coordbox);
       elm_object_text_set(coord_label, phexfile_coordinate_get_name(coord));
       evas_object_smart_callback_add(cosl, "changed", _coord_changed_cb, ctx);
       coord_radio_x = elm_radio_add(coordbox);
       elm_object_text_set(coord_radio_x,"X");
       elm_radio_state_value_set(coord_radio_x, i);
       coord_radio_y = elm_radio_add(coordbox);
       elm_object_text_set(coord_radio_y,"Y");
       elm_radio_state_value_set(coord_radio_y, i);
       if (i==0) {
            ctx->radio_x = coord_radio_x;
            ctx->radio_y = coord_radio_y;
       } else {
            elm_radio_group_add(coord_radio_x, ctx->radio_x);
            elm_radio_group_add(coord_radio_y, ctx->radio_y);
       }
       if (i==1) {
            elm_radio_value_set(ctx->radio_y, 1);
       }

       evas_object_size_hint_align_set(cosl, EVAS_HINT_FILL, 0);
       elm_slider_horizontal_set(cosl, EINA_FALSE);
       elm_slider_unit_format_set(cosl, "%1.2f units");
       elm_slider_indicator_format_set(cosl, "%1.2f units");
       elm_slider_indicator_show_set(cosl, EINA_TRUE);
       elm_slider_min_max_set(cosl, phexfile_coordinate_get_from(coord), phexfile_coordinate_get_steps(coord)-1);
       elm_slider_value_set(cosl, phexfile_coordinate_get_from(coord));
       elm_slider_step_set(cosl, phexfile_coordinate_get_step_width(coord));
       elm_box_pack_end(coordbox, coord_label);
       elm_box_pack_end(coordbox, cosl);
       elm_box_pack_end(coordbox, coord_radio_x);
       elm_box_pack_end(coordbox, coord_radio_y);
       elm_box_pack_end(inner_box, coordbox);
       evas_object_show(coord_label);
       evas_object_show(cosl);
       evas_object_show(coordbox);
       evas_object_show(coord_radio_x);
       evas_object_show(coord_radio_y);
   }

   evas_object_event_callback_add(graph_gl, EVAS_CALLBACK_MOUSE_WHEEL, _zoom_cb, ctx);
   ctx->graph_zoom = 1.0;
   update_table(ctx);
   
   // run the mainloop and process events and callbacks
   elm_run();
   elm_shutdown();
   free(graph_gld);
   free(mol_gld);
   free(ctx->coord_sliders);
   free(ctx);
   return 0;
}
ELM_MAIN()
